/*******************************************************************************
 * Copyright 2007-2008 Vidar Svansson
 *
 * This file is part of JAdapter.
 *
 * JAdapter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAdapter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAdapter.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package org.jadapter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import org.jadapter.exceptions.AdaptationException;
import org.jadapter.func.Filter;
import org.jadapter.func.Function;
import org.jadapter.func.IterableString;
import org.jadapter.func.Lambda;
import org.jadapter.registry.AdapterRegistry;
import org.jadapter.registry.Components;

/**
 * I am debating with myself if I should make this an interface so keep that in 
 * mind if you are using the new operator on this class.
 * 
 * 
 * @author Vidar Svansson
 *
 * @param <T> Generic type of the {@link Iterable} set.
 * 
 * @see AdapterRegistry
 * @see Iterable
 * 
 * @since 0.1
 */
public class Query<T> implements Iterable<T>{

    private Iterable<T> iterable;
    private Lambda<Boolean,T> filter;
    private int length = -1;
    private AdapterRegistry registry;
    private T t;
            
    public Query(T ... t) {
        this(Arrays.asList(t));
        if(t.length == 1) this.t = t[0];
    }    
    
    @SuppressWarnings("unchecked")
    public Query (String string) {
        this.iterable = (Iterable<T>) new IterableString(string);
        this.t = (T)string;
    }

    public Query(Iterable<T> iterable) {
        this(null, iterable); 
    }
    
    public Query(AdapterRegistry registry, T ... t) {
        this(registry, Arrays.asList(t));       
    } 
    
    public Query(AdapterRegistry registry, Iterable<T> iterable) {
        this(registry != null ? registry : Components.getComponentRegistry(), null, iterable);
    }
    
    private Query(AdapterRegistry registry, Lambda<Boolean,T> filter, Iterable<T> iterable){
        this.iterable = iterable;
        this.registry = registry;
        this.filter = filter;
    }
    

    /** 
     * Returns an iterator over the elements in this query.
     * Only elements that pass the filter are returned if the query contains a filter.
     * <br></br><br></br>
     * @see Iterable#iterator
     * @since 0.1
     */
    public Iterator<T> iterator() {
        Lambda<Boolean,T> filter = filter();
        return filter == null ? 
            (Iterator<T>)iterable.iterator() : 
            new QueryIterator(iterable, filter(), this);
    }
    
    /** 
     * Chain this Query with a new filter.
     * 
     * @param filter to be applied to the query.
     * @return a chained Query object containing the filter.
     * @since 0.1
     */
    final public Query<T> filter(Lambda<Boolean,T> filter)  {
        return new Query<T>(registry, filter, this);
    }

    protected Lambda<Boolean,T> filter(){
        return filter;
    }
        
    /** 
     * Slice the query by a given range.
     * 
     * @param x the start index 
     * @param y the end index
     * @return a chained query that iterates from x to y
     * @since 0.1
     */
    final public Query<T> slice(final int x, final int y) {
        return new Query<T>(registry, this){
            @Override protected Lambda<Boolean,T> filter() {
                return new Filter<T>() {
                    int state = 0;
                    public Boolean x(T t) {
                        return ++ state  > x  && state < y ;
                    }     
                };
            }           
        };
    }
    
    /** 
     * Slice the query from a given index
     * 
     * @param x the start index 
     * @return a chained query that iterates from x
     * @since 0.1
     */   
    final public Query<T> slice(final int x) {
        return new Query<T>(registry, this){
            @Override protected Lambda<Boolean,T> filter() {
                return new Filter<T>() {
                    int state = 0;
                    public Boolean x(T t) {
                        return ++ state > x ;
                    }     
                };
            }           
        };        
    } 

    /** 
     * Return the length of this query.
     * If the internal object is a {@link Collection}, then return its size.
     * If the query has never been iterated, then an iteration will be performed.
     * <br></br><br></br>
     * @return the length of this query.
     * @since 0.1
     */
    public int length() {
        if (length > -1) return length;
        if (iterable instanceof Collection) {
            return ((Collection<T>)iterable).size();
        }
        int tmp = 0; 
        for (@SuppressWarnings("unused") T t : this) ++ tmp;
        return length = tmp;
    }    
    
    /**
     * Returns an element in the query by the given index.
     * Currently returns null if not found.
     * <br></br><br></br>
     * @param index
     * @return an element in the query by the given index
     * @since 0.1
     */
    public T get(final int index) {
        int x = 0;
        for (T t : this) {
            if (x ++ == index) {
                return t;
            }
        } return null;
    } 

    /**
     * Create a new Query of elements represented by this Query transformed by the transformer.
     * <br></br><br></br>
     * @param <I> The Generic type of the returned Query.
     * @param transformer the transformer to apply on the elements.
     * @return A new Query object containing the transformed elements.
     * @since 0.1
     */
    final public <I> Query<I> map(Lambda<I,T> transformer) {
        List<I> map = new LinkedList<I>();
        for (T t : this) { map.add(transformer.x(t)); }  
        return new Query<I>(registry, map);
    } 
    
    
    /**
     * @deprecated this is experimental
     * @param fun
     * @return 
     */
    final public T reduce (Function.F2<T, T, T> fun){
        int length = length();
        if (length == 0) 
            throw new IllegalArgumentException("reduce can not work on an empty query");
        T t = get(0);
        return slice(1).reduce(fun, t);
    }
    
    /**
     * @deprecated this is experimental
     * @param fun
     * @param inital
     * @return
     */
    final public T reduce (Function.F2<T, T, T> fun, T inital){
        int length = length();
        if (length == 0) 
            return inital;
        T seed = get(0);
        T result = fun.x(inital, seed);
        if (length == 1)
            return result;
        return slice(1).reduce(fun, result);
    }  
    
     
    /**
     * Transform the object represented by this Query to another type.
     * 
     * @param <C>
     * @param iface the interface to transform to.
     * @return a {@link Proxy} of the interface.
     * @since 0.1
     */
    @SuppressWarnings("unchecked")
    final public <C> C as(final Class<C> iface) {
    	C instance;
    	if(iface.isInterface())    	
    		// 	todo add Query interface to the proxy
    		instance = (C)Proxy.newProxyInstance(iface.getClassLoader(),  new Class[]{iface}, 
    				new QueryInvocationHandler<C,T>(iterable, t , iface, registry));                
    	else {
    		throw new AdaptationException("can only proxy interfaces");
    	} 	
    	return instance;
    }
    
    
    /** 
     * A wrapper around the for(T : Iterable<T>) syntax for chaining 
     * @param lambda the function to apply to each element
     * 
     * @return a reference to this object.
     * @since 0.1
     */
    final public Query<T> each(Lambda<?,T> lambda) {
        for (T t : this) lambda.x(t);
        return this;
    }      
    
    /**
     * A wrapper around method invocation on each element of the query.
     * When invoked, a proxy of type T is returned. 
     * The proxy can then be used to invoke a method on each element.
     * This works best with void methods since it is not possible to return all the results.
     * This currently only works on queries that actually contain at least one element.
     * 
     * 
     * @return a T {@link Proxy} that when invoked, invokes the method on all elements in this query.
     * @since 0.1
     */
    @SuppressWarnings("unchecked")
    final public T each() {
    	// create proxy of T
    	// when proxy method is invoked, invoke on all elements
    	Class<?> klass = this.iterator().next().getClass(); 
    	if(!klass.isInterface()){ // this is illegal
    		Class<?>[] interfaces = klass.getInterfaces();
    		if(interfaces.length == 1) klass = interfaces[0];
    		else throw new IllegalArgumentException
    			(klass.getName() + " is not an interface");
    	}
    	return (T)Proxy.newProxyInstance(klass.getClassLoader(),  new Class[]{klass}, 
				new IteratorInvocationHandler<T>(this));                	    	
    }    
    
    private final class IteratorInvocationHandler<C> implements InvocationHandler {
    	
    	public Query<C> query;
    	
    	public IteratorInvocationHandler(Query<C> query) {
    		this.query = query;
    	}
		
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			for(C c : query) method.invoke(c, args);					
			return null;
		}
    	
    	
    }

    private final class QueryInvocationHandler<C,K> implements InvocationHandler {
    	Iterable<K> iterable;
        Object object;
        C internal; // lazy transformation
        Class<? extends C> klass;
        AdapterRegistry registry;
        
        public QueryInvocationHandler(Iterable<K> iterable, Object object, Class<? extends C> klass, AdapterRegistry registry) {
            this.iterable = iterable;
            this.klass = klass;
            this.registry = registry;
            this.object = object;
        }

        @SuppressWarnings("unchecked")
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            Method m; 

            if (args == null) {
                m = klass.getMethod(method.getName());

            } else {
                Class<?>[] classes = new Class<?>[args.length];
                for(int i = 0; i < args.length; i++){ classes[i] = args[i].getClass(); }
                m = klass.getMethod(method.getName(), classes);                 
            }
            // ok now we need to transform the object
            if (internal == null) {
            	if(registry != null)
            		internal = registry.adapt(klass, iterable);
            		
            	if (internal == null){
            		if(object != null && klass.isInstance(object))            		
            			internal = (C)object;		            	
            		
            		else throw new AdaptationException("Could not adapt to "+klass.getName()
	            					+ " iterable:" + iterable +" object:"+object);         		
            	}
            }
            Object result = m.invoke(internal, args);
            return result;
        }
    }

    private final class QueryIterator implements Iterator<T> {
        int length = 0;
        Iterator<? extends T> iterator;
        
        Lambda<Boolean,T> filter;
        boolean hasNext = false;
        T next = null;
        Query<T> query;

        public QueryIterator(Iterable<? extends T> iterable, Lambda<Boolean,T> filter, Query<T> query) {
            this.iterator = iterable.iterator();
            this.filter = filter;
            this.query = query;
            forward();
        }
        
        private void forward() {
            hasNext = false;
            T current;            
            while (!hasNext && iterator.hasNext()) {
                current = iterator.next();
                if (filter == null || filter.x(current)) {
                    ++ length;

                    next = current;                    
                    hasNext = true;
                }
            }
            if (!iterator.hasNext()) query.length = length;    
        }
        
        public T next() {
            T ret = next;
            forward();
            return ret;
        }

        public boolean hasNext() {
            return hasNext;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }      
    }  
    
    final class JoinedIterator implements Iterator<T>{

        Iterator<? extends T>[] iterators;
        private int status = 0;
        Iterator<? extends T> current;
        
        JoinedIterator(Iterator<? extends T> ... iterators){
            this.iterators = iterators;
            this.current = iterators[status++];
        }
        
        public boolean hasNext() {
            if(current.hasNext()){
                return true;
            }
            if(status < iterators.length) {
                current = iterators[status++];
                return hasNext();
            } return false;           
        }

        public T next() {
            if(current.hasNext()){
                return current.next();
            }
            if(status < iterators.length) {
                current = iterators[status++];
                return next();
            } 
            throw new NoSuchElementException();
        }

        public void remove() {
            throw new UnsupportedOperationException("remove not supported");         
        }
        
    }
    
    /**
     * Prepend a set to this query. 
     * @param iterable the set to prepend
     * @return a chained Query consisting of elements the joined sets.
     * @since 0.1
     */
    public Query<T> prepend(final Iterable<? extends T> iterable){        
        return new Query<T>(registry, this){
            @SuppressWarnings("unchecked")
            @Override public Iterator<T> iterator(){
                return new JoinedIterator(iterable.iterator(), super.iterator());
            }
        };
    } 
        
    /**
     * Append a set to this query. 
     * @param iterable the set to append
     * @return a chained Query consisting of elements the joined sets.
     * @since 0.1
     */    
    public Query<T> append(final Iterable<? extends T> iterable) {        
        return new Query<T>(registry, this){
            @SuppressWarnings("unchecked")
            @Override public Iterator<T> iterator(){
                return new JoinedIterator(super.iterator(), iterable.iterator());
            }
        };
    }
   

    @SuppressWarnings("unchecked")
    public static <T> Query<T> NEW(T t){
        return new Query<T>(t);
    }
    
    public static <T> Query<T> NEW(T ... t){
        return new Query<T>(t);
    }
    
    public static <T> T[] array(T ... t){
        return t;
    }
}